package battle.gameplay;

import java.util.HashSet;

import battle.controllers.BulletController;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.control.PhysicsGhostControl;
import com.jme3.bullet.nodes.PhysicsGhostNode;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

public class Tank extends PhysicsGhostNode implements AnimEventListener{
	
	/**
	 * model[0] = body | model[1] = head
	 * 
	 * dir[0][0] = body looking direction | dir[0][1] = body target direction
	 * dir[1][0] = head looking direction | dir[1][1] = head target direction
	 */
	public float dir[][] = new float[2][2];
	public float pathLength, fireTimer, rollBack, deadTimer;
	public boolean moving, movable;
	public int hitPoints;
	private Vector3f checkPoint;
	private Spatial model[] = new Spatial[2];
	private AnimChannel channel;
	private AnimControl control;
	private GameScene scene;
	private BulletController bullets;
	private BulletAppState bulletState;
	private boolean visible;
	private HashSet <String> commands = new HashSet <String>();
	
	public Tank(GameScene scene, BulletController bullets, BulletAppState bulletState){
		this.scene = scene;
		this.bullets = bullets;
		this.bulletState = bulletState;
	}
	
	public void loadTank(Spatial bodyModel, Spatial headModel){
		model[0] = bodyModel.clone();
		model[1] = headModel.clone();
		control = model[1].getControl(AnimControl.class);
		control.addListener(this);
		channel = control.createChannel();
		channel.setAnim("stand");
		Node node = new Node("tank");
		node.attachChild(model[0]);
		node.attachChild(model[1]);
		attachChild(node);
		BoxCollisionShape tankShape = new BoxCollisionShape(new Vector3f(3f, 3f, 3f));
		setCollisionShape(tankShape);
		addCollideWithGroup(PhysicsGhostControl.COLLISION_GROUP_01);
		addCollideWithGroup(PhysicsGhostControl.COLLISION_GROUP_02);
		addCollideWithGroup(PhysicsGhostControl.COLLISION_GROUP_03);
		setCullHint(CullHint.Always);
		setShadowMode(ShadowMode.Off);
		setName("tank");
		setModelBound(new BoundingBox());
		updateModelBound();
		scale(0.35f);
		setLocalTranslation(scene.width / 2, 0, scene.height / 2);
	}
	
	public void setCheckPoint(Vector3f checkPoint){
		this.checkPoint = checkPoint;
	}
	
	public void locale(){
		setLocalTranslation(checkPoint.x + 0.5f, 0, checkPoint.y + 0.5f);
		model[0].rotate(0, -dir[0][0], 0);
		model[1].rotate(0, -dir[1][0], 0);
		dir[0][0] = checkPoint.z;
		dir[0][1] = checkPoint.z;
		dir[1][0] = checkPoint.z;
		dir[1][1] = checkPoint.z;
		model[0].rotate(0, dir[0][0], 0);
		model[1].rotate(0, dir[1][0], 0);
		setVisible(true);
		setDefaultConditions();
	}
	
	private void setDefaultConditions(){
		pathLength = 0;
		moving = false;
		movable = false;
		rollBack = 0;
	}
	
	public void setVisible(boolean visible){
		if (visible){
			setCullHint(CullHint.Inherit);
			setShadowMode(ShadowMode.CastAndReceive);
			hitPoints = 4;
			bulletState.getPhysicsSpace().add(this);
		}
		else{
			setCullHint(CullHint.Always);
			setShadowMode(ShadowMode.Off);
			System.out.println(this);
			bulletState.getPhysicsSpace().remove(this);
		}
		this.visible = visible;
	}
	
	public void newCommand(String command){
		commands.add(command);
	}
	
	public HashSet <String> getCommands(){
		return commands;
	}
	
	public void setupCommands(){
		for (String command : commands){
			if (command.equals("FastRollBack"))
				rollBack = 3.5f;
			if (command.equals("SlowRollBack"))
				rollBack = 0.8f;
			if (command.equals("HeadSouth")){
				dir[1][1] = Direction.aSouth;
			}
			if (command.equals("HeadEast")){
				dir[1][1] = Direction.aEast;
			}
			if (command.equals("HeadNorth")){
				dir[1][1] = Direction.aNorth;
			}
			if (command.equals("HeadWest")){
				dir[1][1] = Direction.aWest;
			}
			if (command.equals("BodySouth")){
				if (dir[0][1] == Direction.aSouth)
					movable = true;
				else
					dir[0][1] = Direction.aSouth;
			}
			if (command.equals("BodyEast")){
				if (dir[0][1] == Direction.aEast)
					movable = true;
				else
					dir[0][1] = Direction.aEast;
			}
			if (command.equals("BodyNorth")){
				if (dir[0][1] == Direction.aNorth)
					movable = true;
				else
					dir[0][1] = Direction.aNorth;
			}
			if (command.equals("BodyWest")){
				if (dir[0][1] == Direction.aWest)
					movable = true;
				else
					dir[0][1] = Direction.aWest;
			}
			if (command.equals("Fire") && fireTimer == 0)
				fireTimer = 1.3f;
			if (command.equals("Hit"))
				hitPoints -= 1;
		}
		commands.clear();
	}
	
	public boolean update(final float tpf){
		setupCommands();
		if (hitPoints > 0){
			if (rollBack != 0)
				move(-tpf * rollBack);
			else if ((dir[0][1] == dir[0][0] && movable) || moving)
				move(tpf * 8.5f);
			else if (dir[0][1] != dir[0][0] && !moving)
				rotate(0, tpf * 6.5f);
			if (dir[1][1] != dir[1][0])
				rotate(1, tpf * 6.5f);
			fire(tpf);
			return true;
		}
		return false;
	}
	
	protected void move(float dl){
		Vector3f loc;
		moving = true;
		pathLength += dl;
		if (pathLength > 1f || pathLength < 0){
			loc = getLocalTranslation();
			float x = (float) ((int) loc.getX()) + 0.5f;
			float z = (float) ((int) loc.getZ()) + 0.5f;
			setLocalTranslation(x, 0, z);
			setDefaultConditions();
			return;
		}
		if (dir[0][0] == Direction.aSouth)
			move(0, 0, dl);
		else if (dir[0][0] == Direction.aEast)
			move(dl, 0, 0);
		else if (dir[0][0] == Direction.aNorth)
			move(0, 0, -dl);
		else if (dir[0][0] == Direction.aWest)
			move(-dl, 0, 0);
		loc = getLocalTranslation();
		boolean out = loc.getX() < 1.5f || loc.getX() > scene.width - 1.5f || loc.getZ() < 1.5f || loc.getZ() > scene.height - 1.5f;
		if (out){
			float x = (float) ((int) loc.getX()) + 0.5f;
			float z = (float) ((int) loc.getZ()) + 0.5f;
			setLocalTranslation(x, 0, z);
			setDefaultConditions();
		}
	}
	
	protected void rotate(int i, float da){
		if (dir[i][0] < 0)
			dir[i][0] += FastMath.PI * 2;
		if (dir[i][0] > FastMath.PI * 2)
			dir[i][0] -= FastMath.PI * 2;
		if (FastMath.abs(dir[i][1] - dir[i][0]) < da){
			da = FastMath.abs(dir[i][1] - dir[i][0]);
			movable = false;
		}
		if (FastMath.sin(dir[i][1] - dir[i][0]) < 0){
			dir[i][0] -= da;
			model[i].rotate(0, -da, 0);
		}
		else if (FastMath.sin(dir[i][1] - dir[i][0]) > 0){
			dir[i][0] += da;
			model[i].rotate(0, da, 0);
		}
	}
	
	protected void fire(float dt){
		if (fireTimer == 1.3f && !moving && dir[1][1] == dir[1][0]){
			fireTimer -= dt;
			if (!channel.getAnimationName().equals("shoot")){
				channel.setAnim("shoot", 0f);
				channel.setLoopMode(LoopMode.Loop);
				bullets.createBullet(dir[1][0], getLocalTranslation().clone());
			}
		}
		if (fireTimer > 0 && fireTimer != 1.3f){
			fireTimer -= dt;
		}
		if (fireTimer < 0)
			fireTimer = 0;
	}
	
	public boolean dead(float tpf){
		if (deadTimer == 0 && hitPoints == 0){
			deadTimer = 7;
			if (visible)
				setVisible(false);
			return false;
		}
		else if (deadTimer > 0){
			deadTimer -= tpf;
			return true;
		}
		else{
			deadTimer = 0;
			hitPoints = -1;
			return true;
		}
	}
	
	public void onAnimChange(AnimControl control, AnimChannel channel, String animName){}
	
	public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName){
		if (animName.equals("shoot")){
			channel.setAnim("stand", 0f);
			channel.setLoopMode(LoopMode.DontLoop);
			channel.setSpeed(1f);
		}
	}
	
	public boolean isLocalable(){
		return hitPoints == -1;
	}
	
	public boolean isVisible(){
		return visible;
	}
}
